// BUSYBOX MD5 code, repurposed for the function in SlideScript
// Licensing and contributions to the BusyBox development team, changes
// and cleanup done by Bob (OldCoder) Kiraly, 2022

/*
    SlideScript - minimalistic top-down scripting language.
    (C) Copyright 2014-2022 Chris Dorman - some rights reserved (GPLv2)

    View README file supplied with this software for more details
*/

#include "inc/deps.h"
#include "inc/md5.h"

#define ZERO    0
#define ONE     1
#define TWO     2

#define FALSE   0
#define TRUE    1

static inline uint32_md5 rotl32 (uint32_md5 x, unsigned n)
{
    return (x << n) | (x >> (32 - n));
}

static inline uint32_md5 rotr32 (uint32_md5 x, unsigned n)
{
    return (x >> n) | (x << (32 - n));
}

static inline uint64_md5 rotr64 (uint64_md5 x, unsigned n)
{
    return (x >> n) | (x << (64 - n));
}

static inline uint64_md5 rotl64(uint64_md5 x, unsigned n)
{
    return (x << n) | (x >> (64 - n));
}

static void common64_hash
    (md5_ctx_t *ctx, const void *buffer, size_t len)
{
    unsigned bufpos = ctx->total64 & 63;
    ctx->total64 += len;

    while (TRUE)
    {
        unsigned remaining = 64 - bufpos;
        if (remaining > len) remaining = len;
        memcpy (ctx->wbuffer + bufpos, buffer, remaining);
        len -= remaining;
        buffer = (const char *) buffer + remaining;
        bufpos += remaining;
        bufpos -= 64;
        if (bufpos != ZERO) break;
        ctx->process_block (ctx);
    }
}

static void common64_end (md5_ctx_t *ctx, int swap_needed)
{
    unsigned bufpos = ctx->total64 & 63;
    ctx->wbuffer [bufpos++] = 0x80;

    while (1)
    {
        unsigned remaining = 64 - bufpos;
        memset (ctx->wbuffer + bufpos, ZERO, remaining);

        if (remaining >= 8)
        {
            uint64_md5 t = ctx->total64 << 3;
            if (swap_needed) t = bswap_64 (t);
            *(uint64_md5 *) (&ctx->wbuffer [64 - 8]) = t;
        }

        ctx->process_block (ctx);
        if (remaining >= 8) break;
        bufpos = ZERO;
    }
}

static void md5_process_block64 (md5_ctx_t *ctx)
{
    uint32_md5 *words = (void*) ctx->wbuffer;

    uint32_md5 A = ctx->hash [0];
    uint32_md5 B = ctx->hash [1];
    uint32_md5 C = ctx->hash [2];
    uint32_md5 D = ctx->hash [3];

    do { A += (D ^ (B & (C ^ D))) + (*words)   + 0xd76aa478; words++; A = rotl32 (A,  7); A += B; } while (0);
    do { D += (C ^ (A & (B ^ C))) + (*words)   + 0xe8c7b756; words++; D = rotl32 (D, 12); D += A; } while (0);
    do { C += (B ^ (D & (A ^ B))) + (*words)   + 0x242070db; words++; C = rotl32 (C, 17); C += D; } while (0);
    do { B += (A ^ (C & (D ^ A))) + (*words)   + 0xc1bdceee; words++; B = rotl32 (B, 22); B += C; } while (0);
    do { A += (D ^ (B & (C ^ D))) + (*words)   + 0xf57c0faf; words++; A = rotl32 (A,  7); A += B; } while (0);
    do { D += (C ^ (A & (B ^ C))) + (*words)   + 0x4787c62a; words++; D = rotl32 (D, 12); D += A; } while (0);
    do { C += (B ^ (D & (A ^ B))) + (*words)   + 0xa8304613; words++; C = rotl32 (C, 17); C += D; } while (0);
    do { B += (A ^ (C & (D ^ A))) + (*words)   + 0xfd469501; words++; B = rotl32 (B, 22); B += C; } while (0);
    do { A += (D ^ (B & (C ^ D))) + (*words)   + 0x698098d8; words++; A = rotl32 (A,  7); A += B; } while (0);
    do { D += (C ^ (A & (B ^ C))) + (*words)   + 0x8b44f7af; words++; D = rotl32 (D, 12); D += A; } while (0);
    do { C += (B ^ (D & (A ^ B))) + (*words)   + 0xffff5bb1; words++; C = rotl32 (C, 17); C += D; } while (0);
    do { B += (A ^ (C & (D ^ A))) + (*words)   + 0x895cd7be; words++; B = rotl32 (B, 22); B += C; } while (0);
    do { A += (D ^ (B & (C ^ D))) + (*words)   + 0x6b901122; words++; A = rotl32 (A,  7); A += B; } while (0);
    do { D += (C ^ (A & (B ^ C))) + (*words)   + 0xfd987193; words++; D = rotl32 (D, 12); D += A; } while (0);
    do { C += (B ^ (D & (A ^ B))) + (*words)   + 0xa679438e; words++; C = rotl32 (C, 17); C += D; } while (0);
    do { B += (A ^ (C & (D ^ A))) + (*words)   + 0x49b40821; words++; B = rotl32 (B, 22); B += C; } while (0);

    words -= 16;

    do { A += (C ^ (D & (B ^ C))) + words [ 1] + 0xf61e2562; A = rotl32 (A,  5); A += B; } while (0);
    do { D += (B ^ (C & (A ^ B))) + words [ 6] + 0xc040b340; D = rotl32 (D,  9); D += A; } while (0);
    do { C += (A ^ (B & (D ^ A))) + words [11] + 0x265e5a51; C = rotl32 (C, 14); C += D; } while (0);
    do { B += (D ^ (A & (C ^ D))) + words [ 0] + 0xe9b6c7aa; B = rotl32 (B, 20); B += C; } while (0);
    do { A += (C ^ (D & (B ^ C))) + words [ 5] + 0xd62f105d; A = rotl32 (A,  5); A += B; } while (0);
    do { D += (B ^ (C & (A ^ B))) + words [10] + 0x02441453; D = rotl32 (D,  9); D += A; } while (0);
    do { C += (A ^ (B & (D ^ A))) + words [15] + 0xd8a1e681; C = rotl32 (C, 14); C += D; } while (0);
    do { B += (D ^ (A & (C ^ D))) + words [ 4] + 0xe7d3fbc8; B = rotl32 (B, 20); B += C; } while (0);
    do { A += (C ^ (D & (B ^ C))) + words [ 9] + 0x21e1cde6; A = rotl32 (A,  5); A += B; } while (0);
    do { D += (B ^ (C & (A ^ B))) + words [14] + 0xc33707d6; D = rotl32 (D,  9); D += A; } while (0);
    do { C += (A ^ (B & (D ^ A))) + words [ 3] + 0xf4d50d87; C = rotl32 (C, 14); C += D; } while (0);
    do { B += (D ^ (A & (C ^ D))) + words [ 8] + 0x455a14ed; B = rotl32 (B, 20); B += C; } while (0);
    do { A += (C ^ (D & (B ^ C))) + words [13] + 0xa9e3e905; A = rotl32 (A,  5); A += B; } while (0);
    do { D += (B ^ (C & (A ^ B))) + words [ 2] + 0xfcefa3f8; D = rotl32 (D,  9); D += A; } while (0);
    do { C += (A ^ (B & (D ^ A))) + words [ 7] + 0x676f02d9; C = rotl32 (C, 14); C += D; } while (0);
    do { B += (D ^ (A & (C ^ D))) + words [12] + 0x8d2a4c8a; B = rotl32 (B, 20); B += C; } while (0);

    do { A += (B ^ C ^ D)         + words [ 5] + 0xfffa3942; A = rotl32 (A,  4); A += B; } while (0);
    do { D += (A ^ B ^ C)         + words [ 8] + 0x8771f681; D = rotl32 (D, 11); D += A; } while (0);
    do { C += (D ^ A ^ B)         + words [11] + 0x6d9d6122; C = rotl32 (C, 16); C += D; } while (0);
    do { B += (C ^ D ^ A)         + words [14] + 0xfde5380c; B = rotl32 (B, 23); B += C; } while (0);
    do { A += (B ^ C ^ D)         + words [ 1] + 0xa4beea44; A = rotl32 (A,  4); A += B; } while (0);
    do { D += (A ^ B ^ C)         + words [ 4] + 0x4bdecfa9; D = rotl32 (D, 11); D += A; } while (0);
    do { C += (D ^ A ^ B)         + words [ 7] + 0xf6bb4b60; C = rotl32 (C, 16); C += D; } while (0);
    do { B += (C ^ D ^ A)         + words [10] + 0xbebfbc70; B = rotl32 (B, 23); B += C; } while (0);
    do { A += (B ^ C ^ D)         + words [13] + 0x289b7ec6; A = rotl32 (A,  4); A += B; } while (0);
    do { D += (A ^ B ^ C)         + words [ 0] + 0xeaa127fa; D = rotl32 (D, 11); D += A; } while (0);
    do { C += (D ^ A ^ B)         + words [ 3] + 0xd4ef3085; C = rotl32 (C, 16); C += D; } while (0);
    do { B += (C ^ D ^ A)         + words [ 6] + 0x04881d05; B = rotl32 (B, 23); B += C; } while (0);
    do { A += (B ^ C ^ D)         + words [ 9] + 0xd9d4d039; A = rotl32 (A,  4); A += B; } while (0);
    do { D += (A ^ B ^ C)         + words [12] + 0xe6db99e5; D = rotl32 (D, 11); D += A; } while (0);
    do { C += (D ^ A ^ B)         + words [15] + 0x1fa27cf8; C = rotl32 (C, 16); C += D; } while (0);
    do { B += (C ^ D ^ A)         + words [ 2] + 0xc4ac5665; B = rotl32 (B, 23); B += C; } while (0);

    do { A += (C ^ (B | ~D))      + words [ 0] + 0xf4292244; A = rotl32 (A,  6); A += B; } while (0);
    do { D += (B ^ (A | ~C))      + words [ 7] + 0x432aff97; D = rotl32 (D, 10); D += A; } while (0);
    do { C += (A ^ (D | ~B))      + words [14] + 0xab9423a7; C = rotl32 (C, 15); C += D; } while (0);
    do { B += (D ^ (C | ~A))      + words [ 5] + 0xfc93a039; B = rotl32 (B, 21); B += C; } while (0);
    do { A += (C ^ (B | ~D))      + words [12] + 0x655b59c3; A = rotl32 (A,  6); A += B; } while (0);
    do { D += (B ^ (A | ~C))      + words [ 3] + 0x8f0ccc92; D = rotl32 (D, 10); D += A; } while (0);
    do { C += (A ^ (D | ~B))      + words [10] + 0xffeff47d; C = rotl32 (C, 15); C += D; } while (0);
    do { B += (D ^ (C | ~A))      + words [ 1] + 0x85845dd1; B = rotl32 (B, 21); B += C; } while (0);
    do { A += (C ^ (B | ~D))      + words [ 8] + 0x6fa87e4f; A = rotl32 (A,  6); A += B; } while (0);
    do { D += (B ^ (A | ~C))      + words [15] + 0xfe2ce6e0; D = rotl32 (D, 10); D += A; } while (0);
    do { C += (A ^ (D | ~B))      + words [ 6] + 0xa3014314; C = rotl32 (C, 15); C += D; } while (0);
    do { B += (D ^ (C | ~A))      + words [13] + 0x4e0811a1; B = rotl32 (B, 21); B += C; } while (0);
    do { A += (C ^ (B | ~D))      + words [ 4] + 0xf7537e82; A = rotl32 (A,  6); A += B; } while (0);
    do { D += (B ^ (A | ~C))      + words [11] + 0xbd3af235; D = rotl32 (D, 10); D += A; } while (0);
    do { C += (A ^ (D | ~B))      + words [ 2] + 0x2ad7d2bb; C = rotl32 (C, 15); C += D; } while (0);
    do { B += (D ^ (C | ~A))      + words [ 9] + 0xeb86d391; B = rotl32 (B, 21); B += C; } while (0);

    ctx->hash [0] += A;
    ctx->hash [1] += B;
    ctx->hash [2] += C;
    ctx->hash [3] += D;
}

void md5_begin (md5_ctx_t *ctx)
{
    if ((sizeof (uint8_md5 ) != 1) ||
        (sizeof (uint16_md5) != 2) ||
        (sizeof (uint32_md5) != 4) ||
        (sizeof (uint64_md5) != 8))
    {
        fprintf (stderr, "%s\n",
            "Internal error: Host arch not supported");
        exit (ONE);
    }

    ctx->hash [0] = 0x67452301;
    ctx->hash [1] = 0xefcdab89;
    ctx->hash [2] = 0x98badcfe;
    ctx->hash [3] = 0x10325476;

    ctx->total64       = ZERO;
    ctx->process_block = md5_process_block64;
}

void md5_hash (md5_ctx_t *ctx, const void *buffer, size_t len)
{
    common64_hash (ctx, buffer, len);
}

unsigned int md5_end (md5_ctx_t *ctx, void *resbuf)
{
    common64_end (ctx, ZERO);
    memcpy (resbuf, ctx->hash, sizeof (ctx->hash [ZERO]) * 4);
    return  sizeof (ctx->hash [ZERO]) * 4;
}

char *md5_data (char *data, int len)
{
    md5_ctx_t ctx;              // MD5 context
    int ii;                     // Counter

                                // MD5 digest - 16 raw bytes
    unsigned        char digest_raw [16];
                                // MD5 digest - 32-char string  plus a
                                // null terminator
    static unsigned char digest_hex [33];

    md5_begin (&ctx);
    md5_hash  (&ctx, data, len);
    md5_end   (&ctx, digest_raw);

    for (ii = ZERO; ii < 16; ii++)
    {
        sprintf ((char *)digest_hex + (ii * 2), "%02x", digest_raw [ii]);
    }

    return (char *) digest_hex;
}

#define MD5_FILE_BSIZE 8192

char *md5_file (char *ifname)
{
    md5_ctx_t ctx;              // MD5 context
    FILE *ifp;                  // Input-file pointer
    int ii;                     // Counter
    int nb;                     // Number of bytes read

    char data [MD5_FILE_BSIZE]; // Block buffer
                                // MD5 digest - 16 raw bytes
    unsigned        char digest_raw [16];
                                // MD5 digest - 32-char string  plus a
                                // null terminator
    static unsigned char digest_hex [33];

//--------------------------------------------------------------------

    md5_begin (&ctx);

    if ((ifp = fopen (ifname, "rb")) == NULL) return NULL;

    while ((nb = fread (data, ONE, MD5_FILE_BSIZE, ifp)) != ZERO)
        { md5_hash (&ctx, data, nb); }

    fclose (ifp);
    md5_end (&ctx, digest_raw);

    for (ii = ZERO; ii < 16; ii++)
    {
        sprintf ((char *) digest_hex + (ii * 2), "%02x", digest_raw [ii]);
    }

    return (char *) digest_hex;
}

char *md5_string (char *str)
{
    return md5_data (str, strlen (str));
}

#ifdef TESTPROG
void main (int argc, char **argv)
{
    char *ifname;               // Input-file name
    char *input;                // Pointer to input data
    int ii;                     // Counter

    if (argc != TWO)
    {
        puts ("Usage: busymd5 input-file-name");
        exit (ONE);
    }

    ifname = argv [ONE];
    printf ("%s *%s\n", md5_file (ifname), ifname);
}
#endif                          // Endif TESTPROG
